package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Autowired
    private VodService vodService;

    @Autowired
    private RabbitService rabbitService;


    @Autowired
    private RedisTemplate redisTemplate;


    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserFeignClient userFeignClient;

    /**
     * 保存专辑
     * 1.将提交信息封装AlbumInfo对象，保存在专辑信息表
     * 2.将标签值AlbumAttributeValue列表，保存在专辑标签关系表中
     * 3.保存专辑统计记录
     *
     * @param albumInfoVo 专辑VO对象
     * @param userId      用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  //开启事务后默认发生RuntimeException和Error上回滚
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo, Long userId) {
        String content = albumInfoVo.getAlbumTitle() + albumInfoVo.getAlbumIntro();
        String suggest = vodService.scanText(content);
        if ("block".equals(suggest) || "review".equals(suggest)) {
            throw new RuntimeException("内容含有敏感信息，请修改");
        }

        //1.将提交信息封装AlbumInfo对象，保存在专辑信息表
        //1.2 将专辑VO对象转为PO对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        //1.2 为专辑对象中其他属性赋值
        albumInfo.setUserId(userId);
        albumInfo.setTracksForFree(5);
        //1.2.1 审核状态设置为：未通过 todo:后续采用内容审核接口进行安全检测
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        albumInfoMapper.insert(albumInfo);
        Long albumId = albumInfo.getId();

        //2.将标签值AlbumAttributeValue列表，保存在专辑标签关系表中
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
                //2.1 封装AlbumAttributeValue对象
                AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
                albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());
                albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());
                //2.2 关联专辑ID
                albumAttributeValue.setAlbumId(albumId);
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }

        //3.保存专辑统计记录 为新增专辑设置4条初始统计记录
        saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY, 0);
        saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE, 0);
        saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_BUY, 0);
        saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT, 0);

        //4.发送上架消息到MQ，完成自动上架
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, albumId);
    }

    /**
     * 保存专辑统计信息
     *
     * @param albumId  专辑ID
     * @param statType 统计维度 0401-播放量 0402-订阅量 0403-购买量 0403-评论数
     * @param statNum  统计数值
     */
    @Override
    public void saveAlbumStat(Long albumId, String statType, Integer statNum) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        albumStat.setStatNum(statNum);
        albumStatMapper.insert(albumStat);
    }

    /**
     * 分页查询当前用户专辑列表
     *
     * @param pageInfo 分页对象，用于持久层分页查询
     * @param query    查询条件
     * @return
     */
    @Override
    public Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery query) {
        //调用持久层进行分页查询
        return albumInfoMapper.getUserAlbumPage(pageInfo, query);
    }

    @Autowired
    private TrackInfoMapper trackInfoMapper;

    /**
     * 删除专辑
     *
     * @param id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAlbumInfo(Long id) {
        //1.根据专辑ID查询该专辑下是否关联声音 存在则不允许删除
        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TrackInfo::getAlbumId, id);
        queryWrapper.last("limit 1");
        Long count = trackInfoMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new GuiguException(500, "该专辑下存在关联声音");
        }
        //2.根据主键删除专辑记录
        albumInfoMapper.deleteById(id);

        //3.根据专辑ID删除统计记录
        albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId, id));

        //4.根据专辑ID删除专辑标签记录
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, id));

        //5.发送下架专辑消息到MQ
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_LOWER, id);
    }

    /**
     * 封装从数据库获取数据方法
     * 根据专辑ID查询专辑信息，包含标签列表
     *
     * @param id
     * @return
     */
    @Override
    @GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
    public AlbumInfo getAlbumInfoFromDB(Long id) {
        //1.根据专辑ID查询专辑基本信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(id);

        //2.根据专辑ID查询专辑标签列表
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueMapper.selectList(queryWrapper);
        if (CollectionUtil.isNotEmpty(albumAttributeValueList)) {
            albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
        }
        return albumInfo;
    }

    /**
     * 修改专辑信息
     *
     * @param albumInfo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAlbumInfo(AlbumInfo albumInfo) {
        String content = albumInfo.getAlbumTitle() + albumInfo.getAlbumIntro();
        String suggest = vodService.scanText(content);
        if ("block".equals(suggest) || "review".equals(suggest)) {
            throw new RuntimeException("内容含有敏感信息，请修改");
        }
        //1.修改专辑信息
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_NO_PASS);
        albumInfoMapper.updateById(albumInfo);

        //2.删除原有专辑标签记录
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumInfo.getId());
        albumAttributeValueMapper.delete(queryWrapper);

        //3.新增提交专辑标签关系
        List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValue albumAttributeValue : albumAttributeValueVoList) {
                albumAttributeValue.setAlbumId(albumInfo.getId());
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }

        //4.发送上架消息到MQ，完成自动上架
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, albumInfo.getId());

        //TODO 先删除一次缓存
        //TODO 先更新DB
        //TODO 睡眠一段时间后 确保并发读线程将脏数据刷入Redis缓存中
        //TODO 再删除Redis缓存

    }


    //catch处理异常没有抛出
    //同一类中 无事务管理方法 调用自己类内部方法，由于调用当前对象方法 非代理对象方法，造成事务失效
    //方法修饰符设置为 private，无法产生代理对象，造成事务失效
    //采用多线程，导致事务失效 原因：每个线程操作数据库都有各自SqlSession对象
    //数据库中采用MyISAM存储引擎
    @Transactional(rollbackFor = Exception.class)
    public void c() {
        new Thread(() -> {
            //操作A表
        }).start();

        new Thread(() -> {
            //操作B表
        }).start();
    }

    public void a() {
        System.out.println("aa");
        this.b();
    }

    @Transactional(rollbackFor = Exception.class) //产生代理对象
    public void b() {
        System.out.println("bb");
    }

    /**
     * 查询当前用户发布专辑列表，用于渲染下拉框
     *
     * @param userId
     * @return
     */
    @Override
    public List<AlbumInfo> getUserAllAlbumList(Long userId) {
        //1.构建查询条件
        LambdaQueryWrapper<AlbumInfo> queryWrapper = new LambdaQueryWrapper<>();
        //1.1 设置查询条件
        queryWrapper.eq(AlbumInfo::getUserId, userId);
        //1.2 指定查询列
        queryWrapper.select(AlbumInfo::getId, AlbumInfo::getAlbumTitle);
        //1.3 限制返回记录数
        queryWrapper.last("LIMIT 200");
        //1.4 设置排序
        queryWrapper.orderByDesc(AlbumInfo::getId);
        //2.执行条件查询
        return albumInfoMapper.selectList(queryWrapper);
    }

    /**
     * 根据专辑ID查询专辑统计信息
     *
     * @param albumId
     * @return
     */
    @Override
    @GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX + "stat:")
    public AlbumStatVo getAlbumStatVo(Long albumId) {
        return albumInfoMapper.getAlbumStatVo(albumId);
    }

    /***
     * 获取专辑信息
     * 利用分布式缓存Redis优化；采用Redisson分布式锁解决缓存击穿问题，避免大量请求同时查询数据库
     * @param id
     * @return
     */
    @Override
    public AlbumInfo getAlbumInfo(Long id) {
        try {
            //1.优先从分布式缓存Redis获取业务数据,命中缓存直接返回即可
            //1.1 构建缓存业务数据Key
            String dataKey = RedisConstant.ALBUM_INFO_PREFIX + id;
            //1.2 尝试从Redis获取业务数据
            AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 命中缓存返回即可
            if (albumInfo != null) {
                return albumInfo;
            }

            //2.获取分布式锁
            //2.1 构建分布式锁Key 形式：业务key:锁后缀
            String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
            //2.2 创建锁对象
            RLock lock = redissonClient.getLock(lockKey);
            //2.3 尝试获取锁，获取锁成功，执行业务逻辑
            boolean flag = lock.tryLock();

            //3.获取分布式锁成功执行业务逻辑：将数据写入缓存，响应业务数据
            if (flag) {
                try {
                    //3.1 从数据库查询数据 执行封装查询数据库方法
                    albumInfo = this.getAlbumInfoFromDB(id);
                    //3.2 过期时间设置固定时间+随机时间
                    long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(0, 1000);
                    redisTemplate.opsForValue().set(dataKey, albumInfo, ttl, TimeUnit.SECONDS);
                    return albumInfo;
                } finally {
                    //4.释放锁
                    lock.unlock();
                }
            } else {
                //5.获取分布式锁失败，自旋
                return this.getAlbumInfo(id);
            }
        } catch (Exception e) {
            //6.兜底处理方案：当Redis服务不可用时，直接从数据库查询数据
            log.error("[专辑服务]获取专辑信息异常：{}", e);
            return this.getAlbumInfoFromDB(id);
        }
    }

    /**
     * 查询当前用户购买专辑声音-展示分集购买列表
     *
     * @param trackId 选择购买声音ID，以此声音ID作为起始条件
     * @return [{name:"本集",price:1,trackCount:1},{name:"后10集",price:10,trackCount:10},{name:"全集",price:15,trackCount:15}]
     */
    @Override
    public List<Map<String, Object>> findUserTrackPaidList(Long userId, Long trackId) {
        //1.根据预购声音ID查询选择声音信息，得到专辑ID以及声音序号
        TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
        Long albumId = trackInfo.getAlbumId();
        Integer orderNum = trackInfo.getOrderNum();
        //2.根据条件：专辑ID，声音序号 查询"待购买"声音列表
        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TrackInfo::getAlbumId, albumId);
        queryWrapper.ge(TrackInfo::getOrderNum, orderNum);
        queryWrapper.select(TrackInfo::getId);
        List<TrackInfo> waitBuyTrackInfoList = trackInfoMapper.selectList(queryWrapper);
        Assert.notNull(waitBuyTrackInfoList, "待购买声音列表为空");

        //3.远程调用用户服务，获取该专辑下用户已购买声音ID
        List<Long> userPaidTrackIdList = userFeignClient.getUserPaidTrackIdList(albumId).getData();

        //4.判断存在已购买声音，将待购买"声音列表中移除已购买声音
        if (CollectionUtil.isNotEmpty(userPaidTrackIdList)) {
            waitBuyTrackInfoList = waitBuyTrackInfoList
                    .stream()
                    .filter(t -> !userPaidTrackIdList.contains(t.getId()))
                    .collect(Collectors.toList());
        }

        //5.根据最终真正未购买声音ID长度，动态构建分集购买对象集合
        List<Map<String, Object>> list = new ArrayList<>();
        int size = waitBuyTrackInfoList.size();
        //5.0 查询专辑获取声音单价 声音不支持折扣
        AlbumInfo albumInfo = this.getAlbumInfo(albumId);
        BigDecimal price = albumInfo.getPrice();
        //5.1 构建"本集"分集购买对象
        Map<String, Object> currMap = new HashMap<>();
        currMap.put("name", "本集");
        currMap.put("price", price);
        currMap.put("trackCount", 1);
        list.add(currMap);
        //5.2 构建其他分集分集购买对象，例如：后10集、全集等  26
        for (int i = 10; i <= 50; i += 10) {
            if (size > i) {
                Map<String, Object> map = new HashMap<>();
                map.put("name", "后" + i + "集");
                map.put("price", price.multiply(BigDecimal.valueOf(i)));
                map.put("trackCount", i);
                list.add(map);
            } else {
                Map<String, Object> map = new HashMap<>();
                map.put("name", "全集(后" + size + "集)");
                map.put("price", price.multiply(BigDecimal.valueOf(size)));
                map.put("trackCount", size);
                list.add(map);
                break;
            }
        }
        return list;
    }
}
